#!/usr/bin/env python
#
# pinger.py
#
# Version : 0.8
# Date : 20080707
#
# Author : Bernard Czenkusz
# Email  : bernie@skipole.co.uk

#
# pinger.py - ping engine, run as part of skipolemonitor
# Copyright (c) 2007 Bernard Czenkusz
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#

"""pinger uses the os ping function, to ping a list of hosts

It pings the list simultaneously in their own threads, waits for them to finish
and parses the results to return the number of pings for each host and RTT
""" 

import os
import sys
import csv
import net
from threading import Thread


# The name of the file where the language parsing information is kept
LANG_FILE="language.csv"

# The folder where the language file is kept
LANG_FOLDER=net.CONFIG_FOLDER

# Create LANG_LIST which is a list of rows from the LANG_FILE
# each rows contains a language definition to help the windows ping parser
# should the ping used be in different languages

LANG_LIST = []

filepath=os.path.join(LANG_FOLDER, LANG_FILE)

if os.path.exists(filepath):
    # read it
    try:
        fp=open(filepath, "rb")
        reader = csv.reader(fp)
        for row in reader:
            row = tuple(row)
            LANG_LIST.append(row)
        fp.close()
    except:
        LANG_LIST = [("en",": bytes=32 time","Received = "," Average = ")]
else:
    LANG_LIST = [("en",": bytes=32 time","Received = "," Average = ")]

# LANG contains the language tuple used by the windows ping test
# initially it is set with one item as the string "None"
# until the full tuple is discovered from the first ping

LANG = ("None",)

# Create classes used for Exception Errors

class OS_UNSUPPORTED_ERROR(Exception):
    """The pingtest class is not written for this OS"""
    pass

# The class winpingtest is instantiated for each host,
# and when the start method is called, each 'run' is set in a different thread
 
class winpingtest(Thread):
    """Uses the windows os ping to ping a host"""
    def __init__(self, ipaddress):
        """The class is passed the ipaddress of the host to be pinged"""
        Thread.__init__(self)
        self.ipaddress=ipaddress
    def run(self):
        """When the start method is called, this 'run' operates in its own thread"""
        pinger=os.popen("ping -n 4 "+self.ipaddress, "r")
        self.fulloutput=pinger.read()
        pinger.close()
        # Get dictionary of parsing values
        global LANG
        if LANG[0] == "None":
            LANG = getlang(self.fulloutput)
        if LANG[0] == "None":
            self.status= 0
        else:
            self.status=winparse_number(self.fulloutput)
        if self.status == 0:
            self.avertt = 0
        else:
            self.avertt=winparse_avertt(self.fulloutput)
            
            
# A similar class is raised if Skipole Monitor is running on a linux os

class linpingtest(Thread):
    """Uses the linux os ping to ping a host"""
    def __init__(self, ipaddress):
        """The class is passed the ipaddress of the host to be pinged"""
        Thread.__init__(self)
        self.ipaddress=ipaddress
    def run(self):
        """When the start method is called, this 'run' operates in its own thread"""
        pinger=os.popen("ping -c 4 -n -q "+self.ipaddress, "r")
        self.fulloutput=pinger.read()
        pinger.close()
        self.status=linparse_number(self.fulloutput)
        if self.status == 0:
            self.avertt = 0
        else:
            self.avertt=linparse_avertt(self.fulloutput)
 
# The outputs of the ping commands are parsed with the following functions        

def winparse_number(pingoutput):
    """Parses the output of the ping command and returns the number of successfull pings"""
    global LANG
    if LANG[0] == "None": return 0
    if pingoutput.find(LANG[1]) == -1 : return 0
    index=pingoutput.find(LANG[2])
    if index == -1 : return 0
    status=int(pingoutput[index+len(LANG[2]):index+len(LANG[2])+1])
    return status

def linparse_number(pingoutput):
    """Parses the output of the ping command and returns the number of successfull pings"""
    index=pingoutput.find(" received, ")
    if index == -1 : return 0
    status=int(pingoutput[index-1:index])
    return status

def winparse_avertt(pingoutput):
    """Parses the output of the ping command and returns the average RTT"""
    global LANG
    if LANG[0] == "None": return 0
    index=pingoutput.find(LANG[3])
    file_end=pingoutput[index:]
    index2=file_end.find("ms")
    avertt=pingoutput[index+len(LANG[3]):index+index2]
    return int(float(avertt))

def linparse_avertt(pingoutput):
    """Parses the output of the ping command and returns the average RTT"""
    index=pingoutput.find("rtt min/avg/max/mdev = ")
    file_end=pingoutput[index+23:]
    index2=file_end.find("/")
    file_end2=file_end[index2+1:]
    index3=file_end2.find("/")
    avertt=file_end2[:index3]
    return int(float(avertt))
  
            
def pinger(list_of_hosts):
    """pinger is called from the poller module, with a list of hosts to ping

    list_of_hosts is a list of tuples, each tuple contains the host id and ip address.
    Returns another list of hosts, each item of the return list being a list of host id,
    integer value of successful pings, integer value of average RTT"""
    pinglist=[]
    resultlist=[]
    for host in list_of_hosts:
        # for each host
        # host[0] is the host id
        # host[1] is ip address of this host
        #
        # Create a pingtest instance for each host
        if os.name == "nt":
            current=winpingtest(host[1])
        elif os.name == "posix":
            current=linpingtest(host[1])
        else:
            raise OS_UNSUPPORTED_ERROR    
        # Save the host id and pingtest instance in a list
        pinglist.append([host[0], current])
        # start the instance, i.e. the pingtest, in a new thread
        current.start()
    for host in pinglist:
        # for each host
        # host[0] is the host id
        # host[1] is pingtest instance of this host
        #
        # wait for all pings to complete
        host[1].join()
        # Create a results list, being a list of tuples
        # each tuple is the:
        #   host id
        #   integer value of successfull pings
        #   average RTT of the pings
        resultlist.append((host[0], host[1].status, host[1].avertt))
    return resultlist         
    
def getlang(pingoutput):
    """Used by Windows systems which may have the ping command in different languages.

    Given the output of the ping command, detects the language, and returns a tuple
    containing strings and numbers which are used to parse the ping output.
    """
    # Find the correct language.  If it cannot be found, return
    # a tuple with first element "None".
    global LANG_LIST
    # Detect the language
    for lang in LANG_LIST:
        if pingoutput.find(lang[1]) != -1:
            return lang
    # If language not detected, return a tuple with string "None" as the only element
    return ("None",) 




